<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LangFlow 批量测试工具</title>
<style>
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif; margin: 0; padding: 0; background: #f6f7f9; }
  .container { max-width: 1400px; margin: 24px auto; background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.04); }
  header { padding: 16px 20px; border-bottom: 1px solid #eef2f7; display: flex; justify-content: space-between; align-items: center; }
  header h1 { margin: 0; font-size: 20px; }
  .nav-btn { background: #10b981; color: #fff; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-weight: 600; box-shadow: 0 2px 8px rgba(16,185,129,0.3); }
  .content { padding: 20px; }
  .card { border: 1px solid #eef2f7; border-radius: 8px; padding: 16px; background: #fafbfc; margin-bottom: 16px; }
  label { display: block; font-size: 13px; color: #334155; margin: 8px 0 6px; font-weight: 600; }
  input[type=text], input[type=file] { width: 100%; box-sizing: border-box; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 6px; outline: none; font-size: 13px; background: #fff; }
  input[type=radio] { margin-right: 6px; }
  .radio-group { display: flex; gap: 20px; margin-top: 8px; }
  .radio-item { display: flex; align-items: center; }
  button { cursor: pointer; padding: 10px 20px; border-radius: 6px; border: 1px solid #2563eb; background: #2563eb; color: #fff; font-weight: 600; font-size: 14px; }
  button:disabled { background: #94a3b8; border-color: #94a3b8; cursor: not-allowed; }
  button.secondary { background: #fff; color: #1f2937; border-color: #cbd5e1; }
  button.success { background: #10b981; border-color: #10b981; }
  .muted { color: #64748b; font-size: 12px; margin-top: 4px; }
  .progress { margin-top: 12px; }
  .progress-bar { width: 100%; height: 24px; background: #e5e7eb; border-radius: 12px; overflow: hidden; }
  .progress-fill { height: 100%; background: linear-gradient(90deg, #2563eb, #3b82f6); transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 12px; font-weight: 600; }
  .result-table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 13px; }
  .result-table th { background: #f1f5f9; padding: 10px; text-align: left; border: 1px solid #e2e8f0; font-weight: 600; position: sticky; top: 0; }
  .result-table td { padding: 8px 10px; border: 1px solid #e2e8f0; vertical-align: top; max-width: 300px; word-break: break-word; }
  .result-table tr:hover { background: #f8fafc; }
  .status-success { color: #10b981; font-weight: 600; }
  .status-error { color: #ef4444; font-weight: 600; }
  .table-wrapper { max-height: 600px; overflow: auto; margin-top: 12px; border: 1px solid #e2e8f0; border-radius: 6px; }
  pre { white-space: pre-wrap; word-break: break-word; margin: 0; font-size: 12px; font-family: ui-monospace, monospace; }
  .actions { display: flex; gap: 12px; margin-top: 12px; }
  .info-box { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 6px; padding: 12px; margin-top: 12px; }
  .info-box h3 { margin: 0 0 8px 0; font-size: 14px; color: #1e40af; }
  .info-box ul { margin: 4px 0; padding-left: 20px; font-size: 12px; color: #1e40af; }
</style>
</head>
<body>
  <div class="container">
    <header>
      <h1>LangFlow 批量测试工具</h1>
      <a href="/" class="nav-btn">← 单次测试</a>
    </header>
    <div class="content">
      <div class="card">
        <label>LangFlow API URL</label>
        <input id="url" type="text" value="{{ default_url }}" />
        <label style="margin-top: 12px;">session_id 模式</label>
        <div class="radio-group">
          <div class="radio-item">
            <input type="radio" id="session_auto" name="session_mode" value="auto" checked />
            <label for="session_auto" style="margin: 0;">自动生成（每个请求唯一）</label>
          </div>
          <div class="radio-item">
            <input type="radio" id="session_manual" name="session_mode" value="manual" />
            <label for="session_manual" style="margin: 0;">手动输入</label>
          </div>
        </div>
        <input id="session_value" type="text" placeholder="手动输入 session_id（选择手动模式时使用）" style="margin-top: 8px;" />
        <div class="muted">优先级：表格中的 session_id 列 > 此处设置的值</div>
        <label style="margin-top: 12px;">上传测试数据表格</label>
        <input id="file" type="file" accept=".xlsx,.xls,.csv" />
        <div class="muted">支持 Excel (.xlsx, .xls) 和 CSV (.csv) 格式</div>
        <div class="actions">
          <button id="start_test">开始批量测试</button>
          <button id="export_result" class="success" style="display:none;">导出结果</button>
          <button id="download_template" class="secondary">下载表格模板</button>
        </div>
        <div id="progress" class="progress" style="display:none;">
          <div class="progress-bar">
            <div id="progress_fill" class="progress-fill" style="width: 0%;">0%</div>
          </div>
          <div class="muted" style="margin-top: 8px;" id="progress_text">准备中...</div>
        </div>
      </div>
      <div class="info-box">
        <h3>📋 表格格式说明</h3>
        <ul>
          <li><strong>必需列</strong>：<code>input_value</code></li>
          <li><strong>可选列</strong>：<code>session_id</code></li>
          <li><strong>组件参数列</strong>：格式 <code>组件名.键名[类型]</code></li>
        </ul>
      </div>
      <div class="card" id="result_card" style="display:none;">
        <h3 style="margin: 0 0 12px 0;">测试结果</h3>
        <div class="table-wrapper">
          <table class="result-table" id="result_table">
            <thead>
              <tr>
                <th>序号</th>
                <th>session_id</th>
                <th>input_value</th>
                <th>调用耗时(秒)</th>
                <th>流程输出内容</th>
                <th>状态</th>
              </tr>
            </thead>
            <tbody id="result_tbody"></tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
const $ = (id) => document.getElementById(id);
let testResults = [];

$('start_test').onclick = async () => {
  const fileInput = $('file');
  if (!fileInput.files.length) { alert('请先选择要上传的表格文件'); return; }
  const url = $('url').value.trim();
  const sessionMode = document.querySelector('input[name="session_mode"]:checked').value;
  const sessionValue = $('session_value').value.trim();
  const formData = new FormData();
  formData.append('file', fileInput.files[0]);
  formData.append('url', url);
  formData.append('session_mode', sessionMode);
  formData.append('session_value', sessionValue);
  $('start_test').disabled = true;
  $('progress').style.display = 'block';
  $('result_card').style.display = 'block';
  $('export_result').style.display = 'none';
  $('result_tbody').innerHTML = '';
  testResults = [];
  let total = 0; let processed = 0;
  try {
    const resp = await fetch('/api/batch-invoke', { method: 'POST', body: formData });
    if (!resp.ok) { const data = await resp.json(); throw new Error(data.detail || '请求失败'); }
    const reader = resp.body.getReader();
    const decoder = new TextDecoder();
    let buffer = '';
    while (true) {
      const {done, value} = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, {stream: true});
      const lines = buffer.split('\n\n');
      buffer = lines.pop();
      for (const line of lines) {
        if (!line.trim() || !line.startsWith('data: ')) continue;
        try {
          const jsonStr = line.substring(6);
          const event = JSON.parse(jsonStr);
          if (event.type === 'total') { total = event.count; $('progress_text').textContent = `准备处理 ${total} 条数据...`; }
          else if (event.type === 'result') {
            processed++; testResults.push(event.data); addResultRow(event.data);
            const percent = Math.round((processed / total) * 100);
            $('progress_fill').style.width = percent + '%'; $('progress_fill').textContent = percent + '%'; $('progress_text').textContent = `处理中 ${processed}/${total}`;
          } else if (event.type === 'done') {
            $('progress_fill').style.width = '100%'; $('progress_fill').textContent = '100%'; $('progress_text').textContent = `完成！共处理 ${testResults.length} 条数据`; $('export_result').style.display = 'inline-block';
          }
        } catch (e) { console.error('解析事件失败:', e, line); }
      }
    }
  } catch (err) { alert('错误：' + err.message); $('progress_text').textContent = '错误：' + err.message; } finally { $('start_test').disabled = false; }
};

function createExpandableCell(text, maxLength) {
  const fullText = String(text || '');
  if (fullText.length <= maxLength) return `<pre>${escapeHtml(fullText)}</pre>`;
  const previewText = fullText.substring(0, maxLength);
  const hiddenChars = fullText.length - maxLength;
  const cellId = 'cell_' + Math.random().toString(36).substring(2, 9);
  return `
    <div class="text-cell" id="${cellId}">
      <pre class="text-content">
<span class="text-collapsed">${escapeHtml(previewText)}</span><span class="text-full">${escapeHtml(fullText)}</span>
</pre>
      <button class="text-toggle" onclick="toggleTextCell('${cellId}')">...展开 (还有 ${hiddenChars} 个字符)</button>
    </div>
  `;
}

function toggleTextCell(cellId) {
  const cell = document.getElementById(cellId);
  const button = cell.querySelector('.text-toggle');
  const isExpanded = cell.classList.contains('expanded');
  if (isExpanded) { cell.classList.remove('expanded'); const fullText = cell.querySelector('.text-full').textContent; const hiddenChars = fullText.length - 100; button.textContent = `...展开 (还有 ${hiddenChars} 个字符)`; }
  else { cell.classList.add('expanded'); button.textContent = '...折叠'; }
}

function addResultRow(row) {
  const tbody = $('result_tbody');
  const tr = document.createElement('tr');
  const idx = testResults.length;
  const inputCell = createExpandableCell(row.input_value, 100);
  const outputCell = createExpandableCell(row.output_text || row.error, 200);
  tr.innerHTML = `
    <td>${idx}</td>
    <td>${escapeHtml(row.session_id || '')}</td>
    <td>${inputCell}</td>
    <td>${row.duration !== null ? row.duration.toFixed(2) : '-'}</td>
    <td>${outputCell}</td>
    <td class="${row.status === 'success' ? 'status-success' : 'status-error'}">${row.status === 'success' ? '成功' : '失败'}</td>
  `;
  tbody.appendChild(tr);
  tr.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

function displayResults(results) {
  const tbody = $('result_tbody');
  tbody.innerHTML = '';
  results.forEach((row, idx) => {
    const tr = document.createElement('tr');
    const inputCell = createExpandableCell(row.input_value, 100);
    const outputCell = createExpandableCell(row.output_text || row.error, 200);
    tr.innerHTML = `
      <td>${idx + 1}</td>
      <td>${escapeHtml(row.session_id || '')}</td>
      <td>${inputCell}</td>
      <td>${row.duration !== null ? row.duration.toFixed(2) : '-'}</td>
      <td>${outputCell}</td>
      <td class="${row.status === 'success' ? 'status-success' : 'status-error'}">${row.status === 'success' ? '成功' : '失败'}</td>
    `;
    tbody.appendChild(tr);
  });
  $('result_card').style.display = 'block';
}

function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }

$('export_result').onclick = () => {
  const ws_data = [['序号', 'session_id', 'input_value', '调用耗时(秒)', '流程输出内容', '状态']];
  testResults.forEach((row, idx) => { ws_data.push([ idx + 1, row.session_id || '', row.input_value || '', row.duration !== null ? row.duration : '', row.output_text || row.error || '', row.status === 'success' ? '成功' : '失败' ]); });
  const wb = XLSX.utils.book_new();
  const ws = XLSX.utils.aoa_to_sheet(ws_data);
  XLSX.utils.book_append_sheet(wb, ws, '测试结果');
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
  XLSX.writeFile(wb, `langflow_batch_result_${timestamp}.xlsx`);
};

$('download_template').onclick = () => {
  const ws_data = [
    ['session_id', 'input_value', 'SchemaBlueprint.my_json_schema[NestedDict]'],
    ['test_001', '患者姓名：张三，已婚', '{"schema":{"type":"object","properties":{"姓名":{"type":"string","description":"患者姓名"},"婚否":{"type":"string","description":"患者是否已婚"}},"required":["姓名"],"additionalProperties":false},"name":"jsonEntity","strict":true}'],
    ['test_002', '患者姓名：李四，未婚', '{"schema":{"type":"object","properties":{"姓名":{"type":"string","description":"患者姓名"},"婚否":{"type":"string","description":"患者是否已婚"}},"required":["姓名"],"additionalProperties":false},"name":"jsonEntity","strict":true}']
  ];
  const wb = XLSX.utils.book_new();
  const ws = XLSX.utils.aoa_to_sheet(ws_data);
  XLSX.utils.book_append_sheet(wb, ws, '测试数据');
  XLSX.writeFile(wb, 'langflow_batch_template.xlsx');
};
</script>
</body>
</html>